#include "board.h"

#include "tetris.h"
#include "bmploader.h"
#include "brick.h"
#include "collision.h"

__forceinline static void draw_tile(HDC dc, struct bitmap* bmp, int x, int y)
{
  BITMAPINFO bi = {
    // 1                      2           3            4  5   6  7  8  9  10 11
    sizeof(BITMAPINFOHEADER), bmp->width, bmp->height, 1, 32, 0, 0, 0, 0, 0, 0
  };
  int grid_x = x * (TILE_WH + 1) + 1;
  int grid_y = y * (TILE_WH + 1) + 1;
  SetDIBitsToDevice(dc, grid_x, grid_y, bmp->width, bmp->height, 0, 0, 0, bmp->height, bmp->data, &bi, 0);
}

void* create_board(HWND wnd)
{
  int i;
  int size = sizeof(struct tetris_board);
  struct tetris_board* board = (struct tetris_board*)malloc(size);

  memset(board, 0, size);
  board->wnd = wnd;
  board->game_speed = 500;

  srand(GetTickCount());
  board->brick = create_brick(rand() % MAX_TILE_COUNT, rand() % SHAPE_MAX);
  board->brick_x = BRICK_START_X, board->brick_y = BRICK_START_Y;
  board->brick_move_time = GetTickCount();

  for (i = 0; i < MAX_TILE_COUNT; i++) {
    board->brick_tile[i] = get_bmp_data(s_tile_name[i]);
  }
  board->prompt_tile = get_bmp_data(s_prompt_tile);
  return (board);
}

// create & delete memory dc, etc
static void board_draw_enter(struct tetris_board* board)
{
  RECT rc;
  board->dc = GetDC(board->wnd);
  board->mem_dc = CreateCompatibleDC(board->dc);
  if (board->dc == 0 || board->mem_dc == 0) {
    return;
  }
  board->bitmap = CreateCompatibleBitmap(board->dc, WND_W, WND_H);
  SelectObject(board->mem_dc, board->bitmap);
  board->brush = CreateSolidBrush(RGB(255, 255, 255));
  GetClientRect(board->wnd, &rc);
  FillRect(board->mem_dc, &rc, board->brush);
}
static void board_draw_leave(struct tetris_board* board)
{
  BitBlt(board->dc, 0, 0, WND_W, WND_H, board->mem_dc, 0, 0, SRCCOPY);
  DeleteObject(board->bitmap);
  DeleteObject(board->brush);
  DeleteDC(board->mem_dc);
  ReleaseDC(board->wnd, board->dc);
}
static void board_draw_grid(struct tetris_board* board)
{
  int i, j, x, y;
  HGDIOBJ pen = CreatePen(PS_SOLID, 1, 0xFF0000);
  HGDIOBJ old = SelectObject(board->mem_dc, pen);
  for (i = 0; i < WND_W; i += (TILE_WH + 1)) {
    x = i, y = 0;
    MoveToEx(board->mem_dc, x, y, 0);
    x = i, y = WND_H;
    LineTo(board->mem_dc, x, y);
  }
  for (j = 0; j < WND_H; j += (TILE_WH + 1)) {
    x = 0, y = j;
    MoveToEx(board->mem_dc, x, y, 0);
    x = WND_W, y = j;
    LineTo(board->mem_dc, x, y);
  }
  SelectObject(board->mem_dc, old);
  DeleteObject(pen);
}

// remove full row
static void remove_row(struct tetris_board* board, int row)
{
  int x, y;
  for (y = row; y > 0; y--) {
    for (x = 0; x < TILE_X; x++) {
      board->tile_buf[y][x] = board->tile_buf[y - 1][x];
    }
  }
}
static void remove_full_row(struct tetris_board* board)
{
  int x, y;
  for (y = 0; y < TILE_Y; y++) {
    for (x = 0; x < TILE_X; x++) {
      if (board->tile_buf[y][x].exist == 0) {
        break;
      }
    }
    if (x >= TILE_X) {
      remove_row(board, y);
    }
  }
}

// merge the brick into the board tile
static void merge_brick_with_board(struct tetris_board* board)
{
  struct brick_info* brick = board->brick;
  int x, y, board_x, board_y;
  for (y = 0; y < BRICK_RANGE; y++) {
    for (x = 0; x < BRICK_RANGE; x++) {
      if (brick->shape[y][x] != 0) {
        board_x = x + board->brick_x;
        board_y = y + board->brick_y;
        board->tile_buf[board_y][board_x].exist = 1;
        board->tile_buf[board_y][board_x].tile_index = brick->tile_index;
      }
    }
  }

  remove_full_row(board);
}

// put a new brick to the board now
static void put_new_brick(struct tetris_board* board)
{
  change_shape(board->brick, rand() % MAX_TILE_COUNT, rand() % SHAPE_MAX);
  board->brick_x = BRICK_START_X, board->brick_y = BRICK_START_Y;
  board->brick_move_time = GetTickCount();
}

static int check_fail(struct tetris_board* board)
{
  int x;
  for (x = 0; x < TILE_X; x++) {
    if (board->tile_buf[1][x].exist != 0) {
      return (1);
    }
  }
  return (0);
}

static void clear_board(struct tetris_board* board)
{
  memset(board->tile_buf, 0, sizeof(board->tile_buf));
}

// find the prompt brick pos
static void find_propt_pos(struct tetris_board* board, int* x, int* y)
{
  int brick_y;
  char buf[100] = { 0 };
  for (brick_y = board->brick_y; brick_y < TILE_Y; brick_y++) {
    if (check_collision_down(board, board->brick, board->brick_x, brick_y) != 0) {
      break;
    }
  }
  *x = board->brick_x;
  *y = brick_y;

  sprintf(buf, "board->brick_y: %d %d %d\n", board->brick_y, *x, *y);
  OutputDebugString(buf);
}

// check collision, etc...
static void update_brick_status(struct tetris_board* board)
{
  int i, tile_x, tile_y, prompt_x, prompt_y;
  char* brick = (char*)board->brick->shape;
  struct bitmap* bmp;

  // if fail now
  if (check_fail(board) != 0) {
    clear_board(board);
    return;
  }

  if (GetTickCount() - board->brick_move_time > board->game_speed &&
    board->is_paused == 0) {
      if (check_collision_down(board, board->brick, board->brick_x, board->brick_y) == 0) {
        board->brick_y += 1;
      } else {
        merge_brick_with_board(board);
        put_new_brick(board);
      }
      board->brick_move_time = GetTickCount();
  }
  find_propt_pos(board, &prompt_x, &prompt_y);
  for (i = 0; i < BRICK_RANGE*BRICK_RANGE; i++) {
    if (brick[i] != 0) {
      if (bmp = board->brick_tile[board->brick->tile_index]) {
        tile_x = i % BRICK_RANGE + prompt_x;
        tile_y = i / BRICK_RANGE + prompt_y;
        if (board->prompt_tile != 0) {
          draw_tile(board->mem_dc, board->prompt_tile, tile_x, tile_y);
        }

        tile_x = i % BRICK_RANGE + board->brick_x;
        tile_y = i / BRICK_RANGE + board->brick_y;
        draw_tile(board->mem_dc, bmp, tile_x, tile_y);
      }
    }
  }
}

static void draw_tiles(struct tetris_board* board)
{
  int i, j;
  struct tile_info* tile;
  struct bitmap* bmp;
  for (i = 0; i < TILE_X; i++) {
    for (j = 0; j < TILE_Y; j++) {
      tile = &(board->tile_buf[j][i]);
      if (tile->exist == 0 || tile->tile_index < 0 || tile->tile_index >= MAX_TILE_COUNT) {
        continue;
      }
      if ((bmp = board->brick_tile[tile->tile_index]) != 0) {
        draw_tile(board->mem_dc, bmp, i, j);
      }
    }
  }
}

static void move_left(struct tetris_board* board)
{
  if (check_collision_left(board, board->brick, board->brick_x, board->brick_y) == 0) {
    if (board->brick_x > 0) {
      board->brick_x -= 1;
    } else if (board->brick_x == 0) {
      // modify brick structure here
      move_brick_left(board->brick);
    }
  }
}

static void move_right(struct tetris_board* board)
{
  if (check_collision_right(board, board->brick, board->brick_x, board->brick_y) == 0) {
    if (board->brick_x < TILE_X - BRICK_RANGE) {
      board->brick_x += 1;
    } else if (board->brick_x == TILE_X - BRICK_RANGE) {
      // modify brick structure here
      move_brick_right(board->brick);
    }
  }
}

static void move_down_straight(struct tetris_board* board)
{
  while (check_collision_down(board, board->brick, board->brick_x, board->brick_y) == 0) {
    board->brick_y += 1;
  }
}

void board_check_key(void* board, char key)
{
  struct tetris_board* b = (struct tetris_board*)board;
  if (key == VK_ESCAPE) {
    SendMessage(b->wnd, WM_DESTROY, 0, 0);
  } else if (key == 'P') {
    b->is_paused = (b->is_paused == 0) ? 1 : 0;
    SetWindowText(b->wnd, (b->is_paused != 0) ? "TETRIS-PAUSED" : "TETRIS");
  }

  if (b->is_paused != 0) {
    return;
  }
  if (key == VK_UP) {
    change_direction(b->brick);
  } else if (key == VK_LEFT) {
    move_left(board);
  } else if (key == VK_RIGHT) {
    move_right(board);
  } else if (key == ' ') {
    move_down_straight(board);
  }
}

void update_board(void* board)
{
  struct tetris_board* b = (struct tetris_board*)board;
  board_draw_enter(b);

  board_draw_grid(b);
  update_brick_status(b);
  draw_tiles(b);

  board_draw_leave(b);
}

void destroy_board(void* board)
{
  int i;
  struct tetris_board* b = (struct tetris_board*)board;
  for (i = 0; i < MAX_TILE_COUNT; i++) {
    destroy_bmp_data(b->brick_tile[i]);
  }
  free(board);
}
